import {
	Primitive,
	GroundPrimitive,
	PolygonGeometry,
	PolygonHierarchy,
	Cartesian3,
	GeometryInstance,
	MaterialAppearance,
	PolylineMaterialAppearance,
	EntityCollection,
	Material,
	Color,
	VertexFormat,
	WallGeometry,
	PolylineGeometry,
	Matrix4,
	Viewer,
	ScreenSpaceEventHandler,
	ScreenSpaceEventType,
	defined,
	PrimitiveCollection,
	CustomDataSource,
	TextureUniform,
	HeightReference,
	UniformType,
	Rectangle,
	RectangleGeometry
} from 'cesium'
import {
	point as tfPoint,
	buffer as tfBuffer
} from '@turf/turf'
import ProvinceView from '../ProvinceView'
class ProvinceBlock {

	viewer : Viewer | null = null
	isLoad : boolean = false
	isGrowing : boolean = false
	extrudeHeight = 50000

	contentPrimitive : Primitive | null
	wallPrimitive : Primitive | null
	outlinePrimitive : Primitive | null
	platePrimitives : PrimitiveCollection | null
	labelPrimitives : PrimitiveCollection | null

	eventHandler : ScreenSpaceEventHandler | null

	provinceView : ProvinceView | null

	contentColor : Array<any> = [102, 184, 217, '0.2']
	wallColor : Array<any> = [102, 184, 217, 0.1]
	outlineColor : Array<any> = [168, 239, 255, 1.0]
	plateColor : Array<any> = [102, 184, 217, '0.2']
	labelColor : Array<any> = [102, 184, 217, '0.6']

	constructor(viewer : Viewer, provinceView : ProvinceView) {
		this.viewer = viewer
		this.provinceView = provinceView
	}

	async load() {
		if (this.isLoad == true) return
		this.isLoad = true

		const result = await import('../../../../static/gisData/Provinces.json')

		this.createContentPrimitive(result)
		this.createWallPrimitive(result)
		this.createOutlinePrimitive(result)
		this.createPlatePrimitive(result)
		this.createLabelPrimitive(result)

		this.bindEventHandler(this.viewer)
	}

	// 创建省界几何体
	private createContentPrimitive(featureCollection : any) {
		const color = this.contentColor
		const extrudeHeight = this.extrudeHeight

		const instances = []
		featureCollection.features.forEach((feature : any) => {
			const geometry = feature.geometry;
			if (geometry.type == 'Polygon') {
				const coors = geometry.coordinates[0];
				if (coors == null || coors.length < 3) return null

				let lonlats = []
				coors.forEach((coor : Array<number>) => { lonlats = lonlats.concat([...coor, this.extrudeHeight]) })
				const polygonGeom = new PolygonGeometry({
					polygonHierarchy: new PolygonHierarchy(Cartesian3.fromDegreesArrayHeights(lonlats)),
					vertexFormat: VertexFormat.POSITION_AND_ST,
					perPositionHeight: true,
					extrudedHeight: this.extrudeHeight

				})
				const geometryInstance = new GeometryInstance({
					geometry: polygonGeom,
					id: 'country-instance',
				})
				instances.push(geometryInstance)
			}
			else if (geometry.type == 'MultiPolygon') {
				geometry.coordinates.forEach((polygon : any) => {
					const coors = polygon[0];
					if (coors == null || coors.length < 3) return null

					let lonlats = []
					coors.forEach((coor : Array<number>) => { lonlats = lonlats.concat([...coor, this.extrudeHeight]) })

					const polygonGeom = new PolygonGeometry({
						polygonHierarchy: new PolygonHierarchy(Cartesian3.fromDegreesArrayHeights(lonlats)),
						vertexFormat: VertexFormat.POSITION_AND_ST,
						perPositionHeight: true,
						extrudedHeight: this.extrudeHeight

					})
					const geometryInstance = new GeometryInstance({
						geometry: polygonGeom,
						id: 'country-instance',
					})
					instances.push(geometryInstance)
				})
			}
		})

		const appearance = new MaterialAppearance({
			translucent: true,
			closed: true,
			vertexShaderSource: `
				in vec3 position3DHigh;
				in vec3 position3DLow;
				in vec3 normal;
				in vec2 st;
				in float batchId;
				uniform float grow;
				
				out vec3 v_positionEC;
				out vec3 v_positionMC;
				out vec3 v_normalEC;
				out vec2 v_st;
				
				void main()
				{
				    vec4 p = czm_computePosition();
				
				    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates
				    v_positionMC = (czm_inverseModel * vec4(p.xyz, 1.0)).xyz;
				    v_normalEC = czm_normal * normal;                         // normal in eye coordinates
				    v_st = st;
			        v_positionMC.z = v_positionMC.z - (1.0-grow)*${extrudeHeight}.0;
				    gl_Position = czm_modelViewProjectionRelativeToEye * ( czm_model * vec4(v_positionMC,1.0));
				}
			`,
			fragmentShaderSource: `
				in vec3 v_positionEC;
				in vec3 v_positionMC;
				in vec3 v_normalEC;
				in vec2 v_st;
				uniform float grow;
				
				void main()
				{
				    czm_materialInput materialInput;
				    czm_material material = czm_getMaterial(materialInput);
				    out_FragColor = vec4(material.diffuse + material.emission, material.alpha * grow);        
				}
			`,
			material: new Material({
				fabric: {
					type: 'Color',
					uniforms: {
						color: new Color(color[0] / 255, color[1] / 255, color[2] / 255, color[3])
					},
				}
			})
		})
		
		appearance.uniforms = {
			grow: 1
		}
		
		const primitive = new Primitive({
			geometryInstances: instances.filter((item) => { return item != null }),
			appearance: appearance,
			asynchronous: true,
			releaseGeometryInstances: true,
			compressVertices: true,
			allowPicking: false
		})
		this.viewer.scene.primitives.add(primitive)
		this.contentPrimitive = primitive
	}

	// 创建省界墙体
	private createWallPrimitive(featureCollection : any) {
		const color = this.wallColor

		const instances = []
		featureCollection.features.forEach((feature : any) => {
			const geometry = feature.geometry;
			if (geometry.type == 'Polygon') {
				const coors = geometry.coordinates[0];
				if (coors == null || coors.length < 3) return null

				let lonlats = []
				coors.forEach((coor : Array<number>) => { lonlats = lonlats.concat([...coor, this.extrudeHeight]) })
				const wallGeom = new WallGeometry({
					positions: Cartesian3.fromDegreesArrayHeights(lonlats),
				})
				const geometryInstance = new GeometryInstance({
					geometry: wallGeom,
					id: 'province-wall-instance',
				})
				instances.push(geometryInstance)
			}
			else if (geometry.type == 'MultiPolygon') {
				geometry.coordinates.forEach((polygon : any) => {
					const coors = polygon[0];
					if (coors == null || coors.length < 3) return null

					let lonlats = []
					coors.forEach((coor : Array<number>) => { lonlats = lonlats.concat([...coor, this.extrudeHeight]) })

					const wallGeom = new WallGeometry({
						positions: Cartesian3.fromDegreesArrayHeights(lonlats),
					})

					const geometryInstance = new GeometryInstance({
						geometry: wallGeom,
						id: 'province-wall-instance',
					})
					instances.push(geometryInstance)
				})
			}
		})

		const appearance = new MaterialAppearance({
			translucent: true,
			closed: true,
			material: new Material({
				fabric: {
					uniforms: {
						color: new Color(color[0] / 255, color[1] / 255, color[2] / 255, color[3]),
						grow: 1
					},
					source: `
						uniform vec4 color;
						czm_material czm_getMaterial(czm_materialInput materialInput) {
							czm_material material = czm_getDefaultMaterial(materialInput);
							vec2 st = materialInput.st;
							material.diffuse = color.rgb;
							material.alpha = color.a;
							if(st.t > grow) material.alpha = 0.0;
							return material;
						}
					`
				}
			})
		})
		const primitive = new Primitive({
			geometryInstances: instances.filter((item) => { return item != null }),
			appearance: appearance,
			asynchronous: true,
			releaseGeometryInstances: true,
			compressVertices: true,
			allowPicking: false
		})
		this.viewer.scene.primitives.add(primitive)
		this.wallPrimitive = primitive
	}

	// 创建省界分界线
	private createOutlinePrimitive(featureCollection : any) {
		const color = this.outlineColor

		const instances = []
		featureCollection.features.forEach((feature : any) => {
			const geometry = feature.geometry;
			if (geometry.type == 'Polygon') {
				const coors = geometry.coordinates[0];
				if (coors == null || coors.length < 3) return null

				let lonlats = []
				coors.forEach((coor : Array<number>) => { lonlats = lonlats.concat([...coor, this.extrudeHeight]) })
				const outlineGeom = new PolylineGeometry({
					positions: Cartesian3.fromDegreesArrayHeights(lonlats),
				})
				const geometryInstance = new GeometryInstance({
					geometry: outlineGeom,
					id: 'province-outline-instance',
					modelMatrix: Matrix4.IDENTITY.clone(),
				})
				instances.push(geometryInstance)
			}
			else if (geometry.type == 'MultiPolygon') {
				geometry.coordinates.forEach((polygon : any) => {
					const coors = polygon[0];
					if (coors == null || coors.length < 3) return null

					let lonlats = []
					coors.forEach((coor : Array<number>) => { lonlats = lonlats.concat([...coor, this.extrudeHeight]) })

					const outlineGeom = new PolylineGeometry({
						positions: Cartesian3.fromDegreesArrayHeights(lonlats),
					})

					const geometryInstance = new GeometryInstance({
						geometry: outlineGeom,
						id: 'province-outline-instance',
						modelMatrix: Matrix4.IDENTITY.clone(),
					})
					instances.push(geometryInstance)
				})
			}
		})

		const appearance = new PolylineMaterialAppearance({
			translucent: true,
			material: new Material({
				fabric: {
					uniforms: {
						color: new Color(color[0] / 255, color[1] / 255, color[2] / 255, color[3]),
						grow: 1
					},
					source:`
					uniform vec4 color;
					uniform float grow;
					czm_material czm_getMaterial(czm_materialInput materialInput) {
					 	czm_material material = czm_getDefaultMaterial(materialInput);
					 	vec2 st = materialInput.st;
					 	material.diffuse = color.rgb;
					 	material.alpha = color.a * grow;
					 	return material;
					 }
					`
				}
			})
		})
		const primitive = new Primitive({
			geometryInstances: instances.filter((item) => { return item != null }),
			appearance: appearance,
			asynchronous: true,
			releaseGeometryInstances: true,
			compressVertices: true,
			allowPicking: false
		})
		this.viewer.scene.primitives.add(primitive)
		this.outlinePrimitive = primitive
	}

	// 创建省界表面
	private createPlatePrimitive(featureCollection : any) {
		const color = this.plateColor
		const features = featureCollection.features
		const extrudeHeight = this.extrudeHeight
		this.platePrimitives = new PrimitiveCollection()

		features.forEach((featrue : any) => {
			const geom = featrue.geometry
			let polygonGeom = null
			if (geom.type == 'MultiPolygon') {
				let coors = []
				geom.coordinates[0][0].forEach((coor : any) => {
					coors = coors.concat([...coor, this.extrudeHeight])
				})
				polygonGeom = new PolygonGeometry({
					polygonHierarchy: new PolygonHierarchy(Cartesian3.fromDegreesArrayHeights(coors)),
					vertexFormat: VertexFormat.POSITION_AND_ST,
					perPositionHeight: true,
				})
			}
			else if (geom.type == 'Polygon') {
				let coors = []
				geom.coordinates[0].forEach((coor : any) => {
					coors = coors.concat([...coor, this.extrudeHeight])
				})
				polygonGeom = new PolygonGeometry({
					polygonHierarchy: new PolygonHierarchy(Cartesian3.fromDegreesArrayHeights(coors)),
					vertexFormat: VertexFormat.POSITION_AND_ST,
					perPositionHeight: true,
				})
			}
			const geometryInstance = new GeometryInstance({
				geometry: polygonGeom,
				id: `plate-province-instance`,
			})

			const appearance = new MaterialAppearance({
				translucent: true,
				closed: true,
				vertexShaderSource: `
					in vec3 position3DHigh;
					in vec3 position3DLow;
					in vec3 normal;
					in vec2 st;
					in float batchId;
					uniform float grow;
					
					out vec3 v_positionEC;
					out vec3 v_normalEC;
					out vec2 v_st;
					out float v_grow;
					
					void main()
					{
					    vec4 p = czm_computePosition();
					
					    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates
					    v_normalEC = czm_normal * normal;                         // normal in eye coordinates
					    v_st = st;
					    vec4 positionMC = czm_inverseModel * vec4(p.xyz, 1.0);
					    positionMC.z =  positionMC.z - (1.0 - grow)*${extrudeHeight}.0;
					    v_grow = grow;
					    gl_Position = czm_modelViewProjectionRelativeToEye * (czm_model * positionMC);
					}
				`,
				fragmentShaderSource: `
					in vec3 v_positionEC;
					in vec3 v_normalEC;
					in vec2 v_st;
					in float v_grow;
				
					void main()
					{
						vec3 positionToEyeEC = -v_positionEC;
				
						vec3 normalEC = normalize(v_normalEC);
					#ifdef FACE_FORWARD
						normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
					#endif
				
						czm_materialInput materialInput;
						materialInput.normalEC = normalEC;
						materialInput.positionToEyeEC = positionToEyeEC;
						materialInput.st = v_st;
						czm_material material = czm_getMaterial(materialInput);
				
					#ifdef FLAT
						out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
					#else
						out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
					#endif
					}
				
				`,
				material: new Material({
					fabric: {
						uniforms: {
							color: new Color(color[0] / 255, color[1] / 255, color[2] / 255, color[3]),
						},
						source:`
						 uniform vec4 color;
						 czm_material czm_getMaterial(czm_materialInput materialInput) {
						 	czm_material material = czm_getDefaultMaterial(materialInput);
						 	vec2 st = materialInput.st;
						 	material.diffuse = color.rgb;
						 	material.alpha = color.a;
						 	return material;
						 }
						`
					}
				})
			})
			appearance.uniforms = {
				grow: 1
			}
			
			const primitive = new Primitive({
				geometryInstances: geometryInstance,
				appearance: appearance,
				asynchronous: true,
				releaseGeometryInstances: true,
				compressVertices: true
			})
			primitive.code = featrue.properties.CODE
			this.platePrimitives.add(primitive)
		})
		this.viewer.scene.primitives.add(this.platePrimitives)
	}

	// 创建label标签
	private createLabelPrimitive(featureCollection : any) {
		const color = this.plateColor
		const features = featureCollection.features
		this.labelPrimitives = new PrimitiveCollection()

		features.forEach((featrue : any) => {
			const center = [featrue.properties.X, featrue.properties.Y]
			const point = tfPoint(center);
			const buffered = tfBuffer(point, 100, { units: "kilometers" });
			const lonlats = buffered.geometry.coordinates[0]
			const coors = lonlats.map((lonlat : any) => {
				return Cartesian3.fromDegrees(lonlat[0], lonlat[1], this.extrudeHeight)
			})
			const rectangle = Rectangle.fromCartesianArray(coors)
			const geom = new RectangleGeometry({
				rectangle,
				height: this.extrudeHeight
			})
			const geometryInstance = new GeometryInstance({
				geometry: geom,
				id: `plate-province-instance`,
			})
			const labelImage = this.createLabelImage({
				text: featrue.properties.NAME,
				fillColor: this.contentColor,
				textColor: this.labelColor
			})
			const appearance = new MaterialAppearance({
				translucent: true,
				closed: true,
				material: new Material({
					fabric: {
						// type: 'Image',
						uniforms: {
							image: labelImage,
							grow: 1
						},
						source: `
							uniform sampler2D image;
							czm_material czm_getMaterial(czm_materialInput materialInput) {
								czm_material material = czm_getDefaultMaterial(materialInput);
								vec2 st = materialInput.st;
								vec4 imageColor = texture(image, st);
								material.diffuse = imageColor.rgb;
								material.alpha = imageColor.a * grow;
								return material;
							}
						`
					}
				})
			})
			const primitive = new Primitive({
				geometryInstances: geometryInstance,
				appearance: appearance,
				asynchronous: true,
				releaseGeometryInstances: true,
				compressVertices: true,
			})
			this.labelPrimitives.add(primitive)
		})
		this.viewer.scene.primitives.add(this.labelPrimitives)
	}

	// 创建label图片
	private createLabelImage(option : { text : string, fillColor : Array<number>, textColor : Array<number> }) {
		const canvas = document.createElement('canvas');
		canvas.width = 100;
		canvas.height = 100;
		const context = canvas.getContext('2d');
		// 清空画布，背景可设置为透明
		context.fillStyle = `rgba(0,0,0,0)`;
		context.fillRect(0, 0, canvas.width, canvas.height);
		// 设置文字样式并绘制
		context.font = 'bold 24px Microsoft YaHei';
		context.fillStyle = `rgba(${option.textColor[0]}, ${option.textColor[1]}, ${option.textColor[2]}, ${option.textColor[3]})`; // 文字颜色
		context.textAlign = 'center';
		context.textBaseline = 'middle';
		context.fillText(option.text, canvas.width / 2, canvas.height / 2);
		const image = canvas.toDataURL('image/png')
		return image
	}

	// 绑定事件
	private bindEventHandler(viewer : Viewer) {
		var highlightPrimitive = null
		const provinceView = this.provinceView
		if (this.eventHandler != null) this.unbindEventHandler()
		this.eventHandler = new ScreenSpaceEventHandler()
		this.eventHandler.setInputAction(async (event : any) => {
			const pickedObject = viewer.scene.pick(event.position)
			if (defined(pickedObject) && pickedObject.id == 'plate-province-instance') {
				const primitive = pickedObject.primitive
				if (primitive != null) {
					if(provinceView.isLoad) await provinceView.unload()
					provinceView.load(primitive.code)
				}

			}
		}, ScreenSpaceEventType.LEFT_CLICK)
	}

	// 解绑事件
	private unbindEventHandler() {
		if (this.eventHandler == null) return
		this.eventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK)
		this.eventHandler.destroy()
		this.eventHandler = null
	}

	/**
	 * 倒计时帧动画
	 * @param {number} time 倒计时时间
	 * @param {Function} callback 渲染帧回调
	 */
	private countdownFrameRender(time : number, callback : CountdownFrameCallback) {
		const viewer = this.viewer
		return new Promise((resolve) => {
			const originalDate = Date.now()
			const frameFunc = () => {
				const dateNow = Date.now()
				const spend = dateNow - originalDate
				if (spend > time * 1000) {
					viewer.scene.preRender.removeEventListener(frameFunc)
					resolve('')
				}
				let rate = spend / (time * 1000)
				if (rate > 1) rate = 1
				callback(rate)
			}
			viewer.scene.preRender.addEventListener(frameFunc)
		})
	}

	unload() {
		if (this.isLoad == false) return
		this.isLoad = false

		this.viewer.scene.primitives.remove(this.contentPrimitive)
		this.viewer.scene.primitives.remove(this.wallPrimitive)
		this.viewer.scene.primitives.remove(this.outlinePrimitive)
		this.viewer.scene.primitives.remove(this.platePrimitives)
		this.viewer.scene.primitives.remove(this.labelPrimitives)
		//this.contentPrimitive.destroy()
		//this.wallPrimitive.destroy()
		//this.outlinePrimitive.destroy()
		//this.platePrimitives.destroy()
		this.contentPrimitive = null
		this.wallPrimitive = null
		this.outlinePrimitive = null
		this.platePrimitives = null
		this.labelPrimitives = null
		this.unbindEventHandler()

	}

	async show() {
		if (this.isGrowing) return
		this.isGrowing = true
		if (this.isLoad == false) return
		
		this.labelPrimitives.show = true
		this.platePrimitives.show = true
		this.outlinePrimitive.show = true
		this.wallPrimitive.show = true
		this.contentPrimitive.show = true
		 
		await this.countdownFrameRender(1, (rate : number) => {
			for (let i = 0; i < this.labelPrimitives.length; i++) {
				const primitive = this.labelPrimitives.get(i)
				primitive.appearance.material.uniforms.grow = rate
			}
			
			for (let i = 0; i< this.platePrimitives.length; i++) {
				const primitive = this.platePrimitives.get(i)
				primitive.appearance.uniforms.grow = rate
			}
			
			this.outlinePrimitive.appearance.material.uniforms.grow = rate
			this.wallPrimitive.appearance.material.uniforms.grow = rate
			this.contentPrimitive.appearance.uniforms.grow = rate
		})
     
		this.isGrowing = false
	}

	async hide() {
		if (this.isGrowing) return
		this.isGrowing = true
		if (this.isLoad == false) return

		await this.countdownFrameRender(1, (rate : number) => {
			for (let i = 0; i < this.labelPrimitives.length; i++) {
				const primitive = this.labelPrimitives.get(i)
				primitive.appearance.material.uniforms.grow = 1 - rate
			}
			
			for (let i = 0; i< this.platePrimitives.length; i++) {
				const primitive = this.platePrimitives.get(i)
				primitive.appearance.uniforms.grow = 1 - rate
			}
			
			this.outlinePrimitive.appearance.material.uniforms.grow = 1 - rate
			this.wallPrimitive.appearance.material.uniforms.grow = 1 - rate
			this.contentPrimitive.appearance.uniforms.grow = 1 - rate
		})
		
		this.labelPrimitives.show = false
		this.platePrimitives.show = false
		this.outlinePrimitive.show = false
		this.wallPrimitive.show = false
		this.contentPrimitive.show = false

		this.isGrowing = false
	}
}
export default ProvinceBlock

/**
 * 倒计时帧回调
 */
type CountdownFrameCallback = (rate : number) => void